#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <GL/glut.h>
#include "Camera.hpp"
#include "Frustum.hpp"
#include "objreader.h"
#include "HW2.h"
using namespace std;
  // This data structure defines a simple house
  
  int nVerts = 42;    // your vertex array needs to have this many entries
  
  // These are the x,y,z coordinates of the vertices of the triangles
  float vertices[] = {  
            -4,-4,4, 4,-4,4, 4,4,4, -4,4,4,     // front face
            -4,-4,-4, -4,-4,4, -4,4,4, -4,4,-4, // left face
            4,-4,-4,-4,-4,-4, -4,4,-4, 4,4,-4,  // back face
            4,-4,4, 4,-4,-4, 4,4,-4, 4,4,4,     // right face
            4,4,4, 4,4,-4, -4,4,-4, -4,4,4,     // top face
            -4,-4,4, -4,-4,-4, 4,-4,-4, 4,-4,4, // bottom face

            -20,-4,20, 20,-4,20, 20,-4,-20, -20,-4,-20, // grass
            -4,4,4, 4,4,4, 0,8,4,                       // front attic wall
            4,4,4, 4,4,-4, 0,8,-4, 0,8,4,               // left slope
            -4,4,4, 0,8,4, 0,8,-4, -4,4,-4,             // right slope
            4,4,-4, -4,4,-4, 0,8,-4};                   // rear attic wall

  // These are the RGB colors corresponding to the vertices, in the same order
  float colors[] = { 
            1,0,0, 1,0,0, 1,0,0, 1,0,0,  // front is red
            0,1,0, 0,1,0, 0,1,0, 0,1,0,  // left is green
            1,0,0, 1,0,0, 1,0,0, 1,0,0,  // back is red
            0,1,0, 0,1,0, 0,1,0, 0,1,0,  // right is green
            0,0,1, 0,0,1, 0,0,1, 0,0,1,  // top is blue
            0,0,1, 0,0,1, 0,0,1, 0,0,1,  // bottom is blue
  
            0,0.5,0, 0,0.5,0, 0,0.5,0, 0,0.5,0, // grass is dark green
            0,0,1, 0,0,1, 0,0,1,                // front attic wall is blue
            1,0,0, 1,0,0, 1,0,0, 1,0,0,         // left slope is green
            0,1,0, 0,1,0, 0,1,0, 0,1,0,         // right slope is red
            0,0,1, 0,0,1, 0,0,1,};              // rear attic wall is red

  // The index data stores the connectivity of the triangles; 
  // index 0 refers to the first triangle defined above
  int indices[] = {
            0,2,3,    0,1,2,      // front face
            4,6,7,    4,5,6,      // left face
            8,10,11,  8,9,10,     // back face
            12,14,15, 12,13,14,   // right face
            16,18,19, 16,17,18,   // top face
            20,22,23, 20,21,22,   // bottom face
                   
            24,26,27, 24,25,26,   // grass
            28,29,30,             // front attic wall
            31,33,34, 31,32,33,   // left slope
            35,37,38, 35,36,37,   // right slope
            39,40,41};            // rear attic wall

int Window::width = 900;
int Window::height = 900;
int Window::radius = min(Window::height, Window::width)/2;
int mouseTargetX=1;
int mouseTargetY=1;
int mouseTargetZ=1;
Camera camera1 = Camera(Vector3(-10,40,40), Vector3(-5,0,0), Vector3(0,1,0));
static HW2 hw2;

//int nVerts;
//float *vertices;
//float *normals;
//float *texcoords;
//int nIndices;
//int *indices;


//----------------------------------------------------------------------------
// Callback method called when system is idle.
void Window::idleCallback(void)
{
  //cube.spin(.25);       // rotate cube by 1 degree
  //displayCallback();    // call display routine to redraw cube
}

//----------------------------------------------------------------------------
// Callback method called when window is resized.
void Window::reshapeCallback(int w, int h)
{
  width = w;
  height = h;
  glViewport(0, 0, w, h);  // set new viewport size
  //glMatrixMode(GL_PROJECTION);
  //glLoadIdentity();
  //glFrustum(-10.0, 10.0, -10.0, 10.0, 10, 1000.0); // set perspective projection viewing frustum
  //glTranslatef(0, 0, -20);
  //glMatrixMode(GL_MODELVIEW);
  Frustum::genPpersp(60, 1, 1, 100);
  Frustum::setGlProj();
}

//----------------------------------------------------------------------------
// Callback method called when window readraw is necessary or
// when glutPostRedisplay() was called.
void Window::displayCallback(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // clear color and depth buffers
  //glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  //glPopMatrix();
  glLoadMatrixd(hw2.getMatrix().mult(camera1.getInverse()).getPointer());
    //glLoadMatrixd(hw2.getMatrix().getPointer());
  //glPushMatrix();

    //glLoadMatrixd(camera1.getInverse().getPointer());

  //glLoadMatrixd(cube.getMatrix(1).getPointer());
  //createSphere(20,0);
  //glLoadMatrixd(cube.getMatrix(2).getPointer());
  //createSphere(20,2);
  //glLoadMatrixd(cube.getMatrix(3).getPointer());
  //createSphere(20,3);
  HW2::displayHouse();
  
  glFlush();  
  glutSwapBuffers();
}

void HW2::displayHouse() {
	glBegin(GL_TRIANGLES);

	int i, j;
	for (i=0; i<sizeof(indices)/sizeof(int); i++) {
		j = indices[i]*3;
		glColor3f(colors[j], colors[j+1], colors[j+2]);
		glVertex3f(vertices[j], vertices[j+1], vertices[j+2]);
	}

	glEnd();

}
void HW2::mouseClick(int button, int state, int x, int y) {

	x = x-Window::width/2;
	y = -(y-Window::width/2);
	if (x*x+y*y < Window::radius*Window::radius) {
		mouseTargetX = x;
		mouseTargetY = y;
		mouseTargetZ = sqrt((float)(Window::radius*Window::radius-(mouseTargetX*mouseTargetX + mouseTargetY*mouseTargetY)));
	//Vector3 vector = Vector3(mouseTargetX, mouseTargetY, mouseTargetZ);
	//vector = hw2.matrix2.mult3(vector);
	//mouseTargetX = vector[0];
	//mouseTargetX = vector[1];
	//mouseTargetX = vector[2];
	}
}

void HW2::mouseMotion(int x, int y) {

	x = x-Window::width/2;
	y = -(y-Window::height/2);
	if (x*x+y*y < Window::radius*Window::radius) {
		int z = sqrt((float)(Window::radius*Window::radius-(x*x + y*y)));
		
	//Vector3 vector = Vector3(x, y, z);
	//cout <<"1: "<< x<< " " << y << " " << z << endl;
	//vector = hw2.matrix2.mult3(vector);
	//x = vector[0];
	//y = vector[1];
	//z = vector[2];
	//cout << "2: "<<x<< " " << y << " " << z << endl;
		if (x!=0 && y!=0 && z!=0) {
			Vector3 vector1 = Vector3(mouseTargetX, mouseTargetY, mouseTargetZ);
			Vector3 vector2 = Vector3(x,y,z);
			Vector3 vector3 = vector1.cross(vector2);
			if (vector1.length() != 0 && vector2.length()!=0 && vector3.length()!=0){
				double angle = BasicMath::degree(asin(vector3.length()/(vector1.length()*vector2.length())));
				Matrix4 rotationMatrix, transMatrix, invTransMatrix;
				rotationMatrix.rotateArbitrary(vector3, angle);
				transMatrix.translate(camera1.getd().getx(), camera1.getd().gety(), camera1.getd().getz());
				invTransMatrix.translate(-camera1.getd().getx(), -camera1.getd().gety(), -camera1.getd().getz());
				hw2.matrix = hw2.matrix.mult(transMatrix.mult(rotationMatrix.mult(invTransMatrix)));
				//hw2.matrix = invTransMatrix.mult(rotationMatrix.mult(transMatrix.mult(hw2.matrix)));
				//hw2.matrix2 = rotationMatrix.mult(hw2.matrix2);

				mouseTargetX = x;
				mouseTargetY = y;
				mouseTargetZ = z;
				//cout<<"reached"<<endl;	
			    glutPostRedisplay();
			}
		}
	}

}
Matrix4& HW2::getMatrix()
{
  return matrix;
}
//Matrix4& HW2::getMatrix2()
//{
//  return matrix2;
//}

//main stolen from cube for now

int main(int argc, char *argv[])
{
  //float specular[]  = {1.0, 1.0, 1.0, 1.0};
  //float shininess[] = {100.0};
  //float position[]  = {0.0, 10.0, 1.0, 0.0};	// lightsource position
  
  glutInit(&argc, argv);      	      	      // initialize GLUT
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);   // open an OpenGL context with double buffering, RGB colors, and depth buffering
  glutInitWindowSize(Window::width, Window::height);      // set initial window size
  glutCreateWindow("OpenGL Cube");    	      // open window and set window title
  if (glutGet(GLUT_WINDOW_COLORMAP_SIZE) != 0) 
  {
    cerr << "Fatal Error: bad RGBA colormap size\n" << endl;
    exit(1);
  }
  glEnable(GL_DEPTH_TEST);            	      // enable depth buffering
  glClear(GL_DEPTH_BUFFER_BIT);       	      // clear depth buffer
  glClearColor(0.0, 0.0, 0.0, 0.0);   	      // set clear color to black
  //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);  // set polygon drawing mode to fill front and back of each polygon
  glShadeModel(GL_SMOOTH);             	      // set shading to smooth
  glMatrixMode(GL_PROJECTION); 
  glPushMatrix();
  
  // Generate material properties:
  //glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
  //glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
  //glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
  //glEnable(GL_COLOR_MATERIAL);
  
  // Generate light source:
  //glLightfv(GL_LIGHT0, GL_POSITION, position);
  //glEnable(GL_LIGHTING);
  //glEnable(GL_LIGHT0);
  
  // Install callback functions:
  glutDisplayFunc(Window::displayCallback);
  glutReshapeFunc(Window::reshapeCallback);
  glutIdleFunc(Window::idleCallback);
    
  // Initialize cube matrix:
  //cube.getMatrix(1).identity();
  //cube.getMatrix(2).identity();
  //cube.getMatrix(3).identity();


  //nVerts = 0;
  //nIndices = 0;
  //ObjReader::readObj("teapot.obj", nVerts, &vertices, &normals, &texcoords, nIndices, &indices);
  //cout << nVerts << endl;
  //cout << nIndices << endl;

  //int i;
  //for (i = 0; i<nIndices/3; i++) {

  //}
  glutMouseFunc(HW2::mouseClick);
  glutMotionFunc(HW2::mouseMotion);
  
  hw2.getMatrix().identity();
  //hw2.getMatrix2().identity();

  glutMainLoop();
  return 0;
}